﻿//========================================================= 
/**@file protobuf_helper.h
 * @brief Protobuf辅助接口
 * 
 * @date 2018-07-18   17:17:17
 * @author Zhyioo 
 * @version 1.0
 */ 
//--------------------------------------------------------- 
#ifndef _LIBZHOUYB_PROTOBUF_HELPER_H_
#define _LIBZHOUYB_PROTOBUF_HELPER_H_
//--------------------------------------------------------- 
#include "../../include/Base.h"

#include <google/protobuf/descriptor.h>
#include <google/protobuf/message.h>

#include <rapidjson/rapidjson.h>
#include <rapidjson/document.h>

#include <vector>
//--------------------------------------------------------- 
namespace zhou_yb {
namespace wrapper {
namespace protobuf_extractor {
//--------------------------------------------------------- 
/**
 * @brief Protobuf辅助接口
 * @date 2018-07-18 17:18
 */
class ProtobufHelper
{
protected:
    ProtobufHelper() {}

    static rapidjson::Value _FieldToJson(const google::protobuf::Message& msg, 
        const google::protobuf::FieldDescriptor* field, 
        size_t index, rapidjson::Document::AllocatorType& alloc)
    {
        rapidjson::Value json;
        const google::protobuf::Reflection* ref = msg.GetReflection();
        const bool is_repeated = field->is_repeated();
        switch(field->cpp_type())
        {
        #define _GET_FIELD(cpptype, type, api, jsonapi) \
        case google::protobuf::FieldDescriptor::cpptype: \
        { \
            const type val = is_repeated ? \
                ref->GetRepeated##api(msg, field, index) : \
                ref->Get##api(msg, field); \
            json.Set##jsonapi(val); \
            break; \
        }
        _GET_FIELD(CPPTYPE_BOOL, bool, Bool, Bool);
        _GET_FIELD(CPPTYPE_DOUBLE, double, Double, Double);
        _GET_FIELD(CPPTYPE_FLOAT, float, Float, Float);
        _GET_FIELD(CPPTYPE_INT64, google::protobuf::int64, Int64, Int64);
        _GET_FIELD(CPPTYPE_UINT64, google::protobuf::uint64, UInt64, Uint64);
        _GET_FIELD(CPPTYPE_INT32, google::protobuf::int32, Int32, Int);
        _GET_FIELD(CPPTYPE_UINT32, google::protobuf::uint32, UInt32, Uint);
        #undef _GET_FIELD
        
        case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
        {
            std::string str;
            const std::string& val = is_repeated ?
                ref->GetRepeatedStringReference(msg, field, index, &str) :
                ref->GetStringReference(msg, field, &str);
            if(field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
            {
                ByteBuilder buff(32);
                ByteConvert::ToAscii(ByteArray(val.c_str(), val.length()), buff);
                json.SetString(rapidjson::StringRef(buff.GetString(), buff.GetLength()), alloc);
            }
            else
            {
                json.SetString(rapidjson::StringRef(val.c_str(), val.length()), alloc);
            }
            break;
        }
        case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
        {
            const google::protobuf::Message& subMsg = is_repeated ?
                ref->GetRepeatedMessage(msg, field, index) :
                ref->GetMessage(msg, field);
            ToJson(subMsg, json, alloc);
            break;
        }
        case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
        {
            const google::protobuf::EnumValueDescriptor* e = is_repeated ?
                ref->GetRepeatedEnum(msg, field, index) :
                ref->GetEnum(msg, field);
            json.SetInt(e->number());
            break;
        }
        default:
            break;
        }
        return json;
    }
    static bool _JsonToField(const rapidjson::Value& json, 
        google::protobuf::Message& msg, const google::protobuf::FieldDescriptor* field)
    {
        const google::protobuf::Reflection* ref = msg.GetReflection();
        const bool is_repeated = field->is_repeated();
        switch(field->cpp_type())
        {
        #define _SET_FIELD(cpptype, type, api, jsonapi) \
        case google::protobuf::FieldDescriptor::cpptype: \
        { \
            if(!json.Is##jsonapi()) \
                return false; \
            const type& val = json.Get##jsonapi(); \
            if(is_repeated) \
            { \
                ref->Add##api(&msg, field, val); \
            } \
            else \
            { \
                ref->Set##api(&msg, field, val); \
            } \
            break; \
        }
        _SET_FIELD(CPPTYPE_BOOL, bool, Bool, Bool);
        _SET_FIELD(CPPTYPE_DOUBLE, double, Double, Double);
        _SET_FIELD(CPPTYPE_FLOAT, float, Float, Float);
        _SET_FIELD(CPPTYPE_INT64, google::protobuf::int64, Int64, Int64);
        _SET_FIELD(CPPTYPE_UINT64, google::protobuf::uint64, UInt64, Uint64);
        _SET_FIELD(CPPTYPE_INT32, google::protobuf::int32, Int32, Int);
        _SET_FIELD(CPPTYPE_UINT32, google::protobuf::uint32, UInt32, Uint);
        #undef _SET_FIELD
        case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
        {
            if(!json.IsString())
                return false;
            const char* val = json.GetString();
            ByteBuilder buff;
            if(field->type() == google::protobuf::FieldDescriptor::TYPE_BYTES)
            {
                DevCommand::FromAscii(val, buff);
                val = buff.GetString();
            }
            if(is_repeated)
            {
                ref->AddString(&msg, field, val);
            }
            else
            {
                ref->SetString(&msg, field, val);
            }
            break;
        }
        case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
        {
            if(!json.IsObject())
                return false;
            google::protobuf::Message* subMsg = is_repeated ?
                ref->AddMessage(&msg, field) :
                ref->MutableMessage(&msg, field);
            FromJson(json, *subMsg);
            break;
        }
        case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
        {
            const google::protobuf::EnumDescriptor* e = field->enum_type();
            const google::protobuf::EnumValueDescriptor* ev = NULL;
            if(json.IsNumber())
            {
                ev = e->FindValueByNumber(json.GetInt());
            }
            else if(json.IsString())
            {
                ev = e->FindValueByName(json.GetString());
            }
            if(ev == NULL)
                return false;
            if(is_repeated)
            {
                ref->AddEnum(&msg, field, ev);
            }
            else
            {
                ref->SetEnum(&msg, field, ev);
            }
            break;
        }
        default:
            return false;
        }
        return true;
    }
public:
    static size_t ToJson(const google::protobuf::Message& msg, rapidjson::Value& json, rapidjson::Document::AllocatorType& alloc)
    {
        const google::protobuf::Descriptor* d = msg.GetDescriptor();
        const google::protobuf::Reflection* ref = msg.GetReflection();
        if(!d || !ref)
            return 0;
        if(json.IsNull())
        {
            json.SetObject();
        }
        size_t fieldCount = d->field_count();
        size_t count = 0;
        for(size_t i = 0; i < fieldCount; ++i)
        {
            const google::protobuf::FieldDescriptor* field = d->field(i);
            rapidjson::Value val;
            if(field->is_repeated())
            {
                val.SetArray();
                size_t count = ref->FieldSize(msg, field);
                for(size_t index = 0; index < count; ++index)
                {
                    val.PushBack(_FieldToJson(msg, field, index, alloc).Move(), alloc);
                }
            }
            else
            {
                val = _FieldToJson(msg, field, 0, alloc);
            }
            const std::string& name = field->json_name();
            rapidjson::Value key;
            key = rapidjson::StringRef(name.c_str(), name.length());
            json.AddMember(key, val, alloc);
            ++count;
        }
        return count;
    }
    static size_t FromJson(const rapidjson::Value& json, google::protobuf::Message& msg)
    {
        const google::protobuf::Reflection* ref = msg.GetReflection();
        const google::protobuf::Descriptor* d = msg.GetDescriptor();
        if(!d || !ref)
            return 0;
        rapidjson::Value::ConstMemberIterator itr;
        size_t count = 0;
        for(itr = json.MemberBegin(); itr != json.MemberEnd(); ++itr)
        {
            const char* name = itr->name.GetString();
            const google::protobuf::FieldDescriptor* field = d->FindFieldByName(name);
            if(!field)
            {
                field = ref->FindKnownExtensionByName(name);
                if(!field)
                {
                    continue;
                }
            }
            if(field->is_repeated())
            {
                if(!itr->value.IsArray())
                    continue;
                rapidjson::Value::ConstValueIterator itrV = itr->value.Begin();
                bool isOK = false;
                for(; itrV != itr->value.End(); ++itrV)
                {
                    if(_JsonToField(*itrV, msg, field))
                        isOK = true;
                }
                if(isOK) ++count;
            }
            else
            {
                if(_JsonToField(itr->value, msg, field))
                    ++count;
            }
        }
        return count;
    }
};
//--------------------------------------------------------- 
} // namespace protobuf_extractor
} // namespace wrapper
} // namespace zhou_yb
//--------------------------------------------------------- 
#endif // _LIBZHOUYB_PROTOBUF_HELPER_H_
//========================================================= 